import EventEmitter from "events";
import type TypedEmitter from "typed-emitter";
import ArRTM from "ar-rtm-sdk";
import type {
  RtmClient,
  RtmChannel,
  RtmParameters,
  RtmStatusCode,
  RtmMessage,
  ReceivedMessageProperties,
  ChannelAttributes,
} from "ar-rtm-sdk";
import { RTMServiceEvents } from "./liveType";

export class RTMService extends (EventEmitter as unknown as new () => TypedEmitter<RTMServiceEvents>) {
  /**
   * RTM 客户端
   *
   * @private
   * @type {(RtmClient | null)}
   */
  private _rtmClient: RtmClient | null = null;

  /**
   * 是否是传入的 RTM 实例
   *
   * @private
   * @type {boolean}
   */
  private _rtmClientFromOutSide: boolean = false;

  private _rtmConfig: RtmParameters = {};

  /**
   * 是否已经登录 RTC
   *
   * @private
   * @type {boolean}
   */
  private _isLogin: boolean = false;

  /**
   *
   *
   * @private
   * @type {Map<string, {
   *     channel: RtmChannel,
   *     joined: boolean,
   *   }>}
   */
  private _channels: Map<
    string,
    {
      instance: RtmChannel;
      joined: boolean;
    }
  > = new Map();

  constructor() {
    super();

    this._onConnectionStateChange = this._onConnectionStateChange.bind(this);
    this._onPeerMessage = this._onPeerMessage.bind(this);
    this._onTokenWillExpired = this._onTokenWillExpired.bind(this);
    this._onTokenDidExpired = this._onTokenDidExpired.bind(this);
  }

  setParameters(options: RtmParameters) {
    this._rtmConfig = options;
  }

  setInstance(instance: RtmClient) {
    this._rtmClient = instance;
    this._rtmClientFromOutSide = true;
    // 有用户 ID 就是已经登录
    this._isLogin = !!instance.uid;
  }

  /**
   * token 更新
   */
  updataToken(token: string) {
    if (this._rtmClient) {
      this._rtmClient.renewToken(token);
    }
  }

  /**
   * 登录
   * @param {string} appId
   * @param {string} userId
   * @param {string} token
   */

  async login(appId: string, userId: string, token?: string) {
    this._unbindRTMEvents();
    if (!this._isLogin) {
      if (!this._rtmClient) {
        this._rtmClient = ArRTM.createInstance(appId, this._rtmConfig);
      }
      await this._rtmClient.login({
        token,
        uid: userId,
      });
      this._isLogin = true;
    }
    this._bindRTMEvents();
  }

  logout() {
    if (this._isLogin) {
      this.leaveChannels();
      // 不是来源是外部
      if (!this._rtmClientFromOutSide) {
        this._rtmClient?.logout();
        this._rtmClient = null;
        this._rtmClientFromOutSide = false;
        this._rtmConfig = {};
      }
      this._unbindRTMEvents();
      this._isLogin = false;
    }
  }

  async joinChannel(channelId: string) {
    if (this._isLogin) {
      const channelInfo = this._channels.get(channelId);
      if (!channelInfo) {
        const channelInstance = this._rtmClient!.createChannel(channelId);
        this._bindRTMChannelEvents(channelInstance);
        await channelInstance?.join();
        this._channels.set(channelId, {
          instance: channelInstance,
          joined: true,
        });
      }
    }
  }

  async leaveChannel(channelId: string) {
    if (this._isLogin) {
      const channelInfo = this._channels.get(channelId);
      if (channelInfo) {
        this._unbindRTMChannelEvents(channelInfo.instance);
        await channelInfo.instance?.leave();
        this._channels.delete(channelId);
      }
    }
  }

  async sendP2PMessage(userId: string, messageContent: string) {
    if (this._isLogin) {
      await this._rtmClient?.sendMessageToPeer(
        {
          text: messageContent,
        },
        userId
      );
    }
  }

  async sendChannelMessage(channelId: string, messageContent: string) {
    if (this._isLogin) {
      const channelInfo = this._channels.get(channelId);
      if (channelInfo) {
        await channelInfo.instance?.sendMessage(
          {
            text: messageContent,
          },
          { enableHistoricalMessaging: true }
        );
      }
    }
  }

  async leaveChannels() {
    if (this._isLogin) {
      this._channels.forEach((channelInfo, channelId) => {
        this.leaveChannel(channelId);
      });
      this._channels.clear();
    }
  }

  destroy() {
    this.leaveChannels();
    this.logout();
  }

  private _onConnectionStateChange(
    newState: RtmStatusCode.ConnectionState,
    reason: RtmStatusCode.ConnectionChangeReason
  ) {
    this.emit("ConnectionStateChange", newState, reason);
  }

  private _onPeerMessage(
    message: RtmMessage,
    peerId: string,
    messageProps: ReceivedMessageProperties
  ) {
    this.emit("MessageFromPeer", message, peerId);
  }

  private _onTokenWillExpired() {
    this.emit("RTMTokenWillExpired");
  }
  private _onTokenDidExpired() {
    this.emit("RTMTokenDidExpired");
  }

  private _bindRTMEvents() {
    this._rtmClient?.on(
      "ConnectionStateChanged",
      this._onConnectionStateChange
    );
    this._rtmClient?.on("MessageFromPeer", this._onPeerMessage);
    this._rtmClient?.on("TokenWillExpired", this._onTokenWillExpired);
    this._rtmClient?.on("TokenDidExpired", this._onTokenDidExpired);
  }

  private _unbindRTMEvents() {
    this._rtmClient?.off(
      "ConnectionStateChanged",
      this._onConnectionStateChange
    );
    this._rtmClient?.off("MessageFromPeer", this._onPeerMessage);
    this._rtmClient?.off("TokenWillExpired", this._onTokenWillExpired);
    this._rtmClient?.off("TokenDidExpired", this._onTokenDidExpired);
  }

  private _onChannelMessage(
    message: RtmMessage,
    memberId: string,
    messagePros: ReceivedMessageProperties
  ) {
    this.emit("ChannelMessage", message, memberId);
  }

  private _onChannelMemberJoined(memberId: string) {
    this.emit("ChannelMemberJoined", memberId);
  }

  private _onChannelMemberLeft(memberId: string) {
    this.emit("ChannelMemberLeft", memberId);
  }
  private _onChannelMemberCountUpdated(memberCount: number) {
    this.emit("ChannelMemberCountUpdated", memberCount);
  }
  private _onChannelAttributesUpdated(attributes: ChannelAttributes) {
    this.emit("ChannelAttributesUpdated", attributes);
  }

  private _bindRTMChannelEvents(channelInstance: RtmChannel) {
    this._onChannelMessage = this._onChannelMessage.bind(this);
    this._onChannelMemberJoined = this._onChannelMemberJoined.bind(this);
    this._onChannelMemberLeft = this._onChannelMemberLeft.bind(this);
    this._onChannelMemberCountUpdated =
      this._onChannelMemberCountUpdated.bind(this);
    this._onChannelAttributesUpdated =
      this._onChannelAttributesUpdated.bind(this);

    channelInstance?.on("ChannelMessage", this._onChannelMessage);
    channelInstance?.on("MemberJoined", this._onChannelMemberJoined);
    channelInstance?.on("MemberLeft", this._onChannelMemberLeft);
    channelInstance?.on(
      "MemberCountUpdated",
      this._onChannelMemberCountUpdated
    );
    channelInstance?.on("AttributesUpdated", this._onChannelAttributesUpdated);
  }

  private _unbindRTMChannelEvents(channelInstance: RtmChannel) {
    channelInstance?.off("ChannelMessage", this._onChannelMessage);
    channelInstance?.off("MemberJoined", this._onChannelMemberJoined);
    channelInstance?.off("MemberLeft", this._onChannelMemberLeft);
    channelInstance?.off(
      "MemberCountUpdated",
      this._onChannelMemberCountUpdated
    );
    channelInstance?.off("AttributesUpdated", this._onChannelAttributesUpdated);
  }
}
